{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "2e2aa08d",
   "metadata": {},
   "source": [
    "\n",
    "Copyright(c) 2025 NVIDIA Corporation. All rights reserved.\n",
    "\n",
    "NVIDIA Corporation and its licensors retain all intellectual property\n",
    "and proprietary rights in and to this software, related documentation\n",
    "and any modifications thereto.Any use, reproduction, disclosure or\n",
    "distribution of this software and related documentation without an express\n",
    "license agreement from NVIDIA Corporation is strictly prohibited."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Creating Your Own Frame Processor\n",
    "\n",
    "Frame processors are responsible for handling frames in a pipeline.  \n",
    "A frame processor can analyze, modify, block, or pass frames based on custom logic.  \n",
    "In this tutorial, we will guide you step-by-step on how to create your own frame processor."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4714f5a0",
   "metadata": {},
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 1: Import the Necessary Modules\n",
    "\n",
    "Create a file at the same level as `bot.py` and name it `my_custom_processor.py`.  \n",
    "\n",
    "![Avatar Controller Config](pictures/ace_controller_bot.png)\n",
    "\n",
    "\n",
    "Before starting, you need to import the required modules and classes. An example shown below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from loguru import logger\n",
    "from pipecat.frames.frames import (\n",
    "    Frame,  # Base class for all types of frames\n",
    "    TextFrame  # A specific type of frame\n",
    ")\n",
    "from pipecat.processors.frame_processor import FrameProcessor, FrameDirection  # Base class for frame processors"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "64264ba7",
   "metadata": {},
   "source": [
    "Note that if the modules are not already added as project dependencies, you need to add them in the pyproject.toml as well\n",
    "\n",
    "The pyproject.toml is located in the downloaded folder with the directory path: \n",
    "\n",
    "```\n",
    "ace-controller-ace-controller-deployment -> assets -> app-storage-volume -> pyproject.toml\n",
    "```\n",
    "\n",
    "A sample snippet from pyroject.toml shown below. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c281bf62",
   "metadata": {},
   "outputs": [],
   "source": [
    "[project]\n",
    "name = \"tokkio-llm-rag-example\"\n",
    "version = \"0.1.0\"\n",
    "description = \"Add your description here\"\n",
    "readme = \"README.md\"\n",
    "requires-python = \">=3.12\"\n",
    "dependencies = [\n",
    "    \"nvidia-pipecat\",\n",
    "    \"opentelemetry-sdk==1.31.1\",\n",
    "    \"opentelemetry-exporter-otlp-proto-grpc==1.31.1\",\n",
    "    \"opentelemetry-distro==0.52b1\",\n",
    "    \"watchfiles==1.0.4\",\n",
    "    \"watchdog==6.0.0\",\n",
    "    # <----- Add the dependencies here\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 2: Define the Processor Class\n",
    "\n",
    "Now, define your custom frame processor by extending `FrameProcessor`.  \n",
    "Your class can have additional attributes depending on your requirements. Feel free to use the `guardrail.py` as an example for reference."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class CustomProcessor(FrameProcessor):\n",
    "    \"\"\"\n",
    "    CustomProcessor is a user-defined frame processor that demonstrates\n",
    "    how to manipulate and monitor frames in a ace controller pipeline.\n",
    "\n",
    "    Args:\n",
    "        custom_message (str): A custom message to be sent when processing specific frames.\n",
    "    \"\"\"\n",
    "    def __init__(self, custom_message=\"Default Message\", **kwargs):\n",
    "        super().__init__(**kwargs)  # Forward additional arguments to the parent class\n",
    "        self._custom_message = custom_message  # Store the custom message\n",
    "\n",
    "    def modify_message(self, message: str) -> str:\n",
    "        \"\"\"\n",
    "        Modify a message as part of your frame processing logic. \n",
    "        This is a placeholder method, and you can customize it as needed.\n",
    "\n",
    "        Args:\n",
    "            message (str): The original message.\n",
    "        \n",
    "        Returns:\n",
    "            str: The modified message.\n",
    "        \"\"\"\n",
    "        # Example of a simple message transformation\n",
    "        return f\"Modified: {message}\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 3: Implement the `process_frame` Method\n",
    "\n",
    "The core logic of your frame processor is implemented in the `process_frame` method.  \n",
    "This method handles frames sent through the pipeline."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "    async def process_frame(self, frame: Frame, direction: FrameDirection):\n",
    "        \"\"\"\n",
    "        Processes incoming or outgoing frames.\n",
    "\n",
    "        Args:\n",
    "            frame (Frame): The incoming or outgoing frame.\n",
    "            direction (FrameDirection): The direction of the frame (incoming or outgoing).\n",
    "        \"\"\"\n",
    "        # Call the parent class's process_frame to maintain default behavior\n",
    "        await super().process_frame(frame, direction)\n",
    "\n",
    "        # Perform custom processing logic\n",
    "        if isinstance(frame, TextFrame):\n",
    "            # Do something with the TextFrame/ modify it\n",
    "\n",
    "            # Push the modified or original frame downstream\n",
    "            await self.push_frame(frame, direction)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 4: Integrate Your Processor into the Pipeline\n",
    "\n",
    "Once your processor is implemented, you can register it in your `pipeline` in the `bot.py`.\n",
    "\n",
    "Ensure to include it correctly in the path (you can refer to the `guardrail` as an example.)\n",
    "\n",
    "Also, ensure that the expected frame is available in the pipeline and that it the processor outputs a frame that is used by some component in the pipeline."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 5: Test Your Frame Processor\n",
    "\n",
    "Now, you can test your custom frame processor.  \n",
    "Ensure that the frames are processed as expected, and any custom modifications or actions are applied successfully.\n",
    "\n",
    "---\n",
    "\n",
    "### Congratulations!\n",
    "\n",
    "You have successfully created and integrated your own frame processor into the bot pipeline.  \n",
    "Feel free to customize your processor further based on your specific requirements!"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.16"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
